package com.elves.gateway.authorization;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.alibaba.nacos.shaded.com.google.gson.JsonObject;
import com.elves.gateway.constant.AuthConstant;
import com.elves.gateway.constant.RedisConstant;
import com.nimbusds.jose.JWSObject;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 鉴权管理器，用于判断是否有资源的访问权限
 * Created by macro on 2020/6/19.
 */
@Component
@Slf4j
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
        log.info("authorizationContext:{}", authorizationContext);
        JSONArray roles = new JSONArray();
        String sub = null;
        ServerWebExchange exchange = authorizationContext.getExchange();
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StrUtil.isEmpty(token)) {
            log.error("token is empty");
        } else {
            try {
                log.info("token : {}", token);
                //从token中解析用户信息并设置到Header中去
                String realToken = token.replace("Bearer ", "");
                JWSObject jwsObject = JWSObject.parse(realToken);
                log.info("jwsObject : {}", jwsObject);
                String userStr = jwsObject.getPayload().toString();
                log.info("AuthGlobalFilter.filter() user: {}", userStr);
                log.info("jwsObject.getHeader : {}", jwsObject.getHeader());
                JSONObject json = JSONObject.parseObject(userStr);
                sub = json.getString("sub");
                roles = json.getJSONArray("roles");
                log.info("user [" + sub + "] has roles:{}", roles);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //从Redis中获取当前路径可访问角色列表
        URI uri = authorizationContext.getExchange().getRequest().getURI();
        log.info("uri:{}", uri);
        log.info("uri:{}", uri.getPath());
        log.info("redisTemplate:{}", redisTemplate.opsForHash());
//        List<String> authorities = new ArrayList<>();
//        HashOperations<String, Object, Object> roleHashTable = redisTemplate.opsForHash();
//        PathMatcher pathMatcher = new AntPathMatcher();
//        for(Object uriRule: roleHashTable.keys(RedisConstant.RESOURCE_ROLES_MAP)){
//            for(String uriByRole: (Set<String>)uriRule){
//                if(pathMatcher.match(uriByRole,uri.getPath())){
//                    authorities.add( roleHashTable.get(RedisConstant.RESOURCE_ROLES_MAP,uriByRole).toString());
//                }
//            }
//        }
//        Object obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath());
//        List<String> authorities = Convert.toList(String.class, obj);

        Map<String, List<String>> resourceRolesMap = new TreeMap<>();

//app的controller
        resourceRolesMap.put("/eapi/vip", CollUtil.toList("VIP"));
        resourceRolesMap.put("/eapp/vip", CollUtil.toList("VIP"));
        resourceRolesMap.put("/eapi/hello", CollUtil.toList("ADMIN"));
        resourceRolesMap.put("/eapi/user/currentUser", CollUtil.toList("ADMIN", "TEST", "USER"));

// Resource server ddns
        resourceRolesMap.put("/edns/vip/*", CollUtil.toList("VIP"));
        resourceRolesMap.put("/edns/admin/*", CollUtil.toList("ADMIN"));
        resourceRolesMap.put("/edns/user/*", CollUtil.toList("USER"));

// AUTH server的controller
        resourceRolesMap.put("/eauth/mine", CollUtil.toList("ADMIN"));
        resourceRolesMap.put("/eauth/time", CollUtil.toList("ADMIN", "VIP"));

//Gateway的controller
        resourceRolesMap.put("/admin", CollUtil.toList("ADMIN"));
        resourceRolesMap.put("/vip", CollUtil.toList("VIP"));
        resourceRolesMap.put("/user", CollUtil.toList("USER"));
        resourceRolesMap.put("/see", CollUtil.toList("ADMIN", "TEST", "USER", "VIP"));
        resourceRolesMap.put("/me", CollUtil.toList("ADMIN", "TEST", "USER", "VIP"));

        List<String> authorities = new ArrayList<>();
        PathMatcher pathMatcher = new AntPathMatcher();
        for (String uriRole : resourceRolesMap.keySet()) {
            if (pathMatcher.match(uriRole, uri.getPath())) {
                authorities.addAll(resourceRolesMap.get(uriRole));
            }
        }
        log.info("need role authorities:{}", authorities);

        //  获取访问资源所需的角色
//        String path = uri.getPath();
//        List<String> authorizedRoles = new ArrayList<>();   //  拥有访问权限的角色
//        Map<Object, Object> urlRoleMap = redisTemplate.opsForHash().entries(RedisConstant.RESOURCE_ROLES_MAP);
//        for (Map.Entry<Object, Object> entry : urlRoleMap.entrySet()) {
//            String permissionUrl = (String) entry.getKey();
//            List<String> rolesNeed = JSON.parseArray((String) entry.getValue(), String.class);
//            if (pathMatcher.match(permissionUrl, path)) {
//                authorizedRoles.addAll(rolesNeed);
//            }
//        }

        log.info("need authorities:{}", authorities);
        //  没有配置权限规则表示无需授权，直接放行
        if (CollectionUtils.isEmpty(authorities)) {
            log.info("gateway 没说要什么角色权限，赋值为true");
            return Mono.just(new AuthorizationDecision(true));
        } else {
            //逐条规则判断请求是否含有对应role
//            boolean hasRole = false;
            for (String role : authorities) {
                if (roles.contains(role)) {
                    log.info("user [" + sub + "] get access! use role:{}", role);
//                    hasRole = true;
                    return Mono.just(new AuthorizationDecision(true));
                }
            }
//            if (hasRole) {
//                return Mono.just(new AuthorizationDecision(true));
//            }
        }
        //加ROLE_前綴
        authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());
        log.info("authorities:{}", authorities);
//        log.info("mono:{}",mono.checkpoint());
//        log.info("monoFFFF:{}", mono.filter(Authentication::isAuthenticated).flatMapIterable(Authentication::getAuthorities).map(GrantedAuthority::getAuthority));
//        log.info("monoFFFF:{}", mono.filter(Authentication::isAuthenticated).flatMapIterable(Authentication::getAuthorities).map(GrantedAuthority::getAuthority).any(authorities::contains));
        //认证通过且角色匹配的用户可访问当前路径
        return mono
                .filter(Authentication::isAuthenticated).flatMapIterable(Authentication::getAuthorities)
                .map(GrantedAuthority::getAuthority).any(authorities::contains)
                .map(AuthorizationDecision::new).defaultIfEmpty(new AuthorizationDecision(false));
    }


}
